package com.ssd.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ssd.entity.dto.request.ActivityQueryForPageReqDTO;
import com.ssd.enums.ActivityStatusEnum;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ssd.constants.RedisConstants;
import com.ssd.constants.TabTypeConstants;
import com.ssd.entity.Activity;
import com.ssd.entity.Coupon;
import com.ssd.entity.dto.request.ActivitySaveReqDTO;
import com.ssd.entity.dto.response.ActivityInfoResDTO;
import com.ssd.entity.dto.response.SeizeCouponInfoResDTO;

import com.ssd.enums.CouponStatusEnum;
import com.ssd.exception.LogicException;
import com.ssd.mapper.ActivityMapper;
import com.ssd.service.IActivityService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ssd.service.ICouponService;
import com.ssd.service.ICouponWriteOffService;
import com.ssd.utils.*;
import com.ssd.viewmodel.Pager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.ssd.enums.ActivityStatusEnum.*;

/**
 * <p>
 * 优惠券活动表 服务实现类
 * </p>
 *
 * @author author
 * @since 2024-11-21
 */
@Service
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {
    @Resource
    private ICouponWriteOffService couponWriteOffService;
    @Resource
    private ICouponService couponService;
    @Resource
    private RedisTemplate redisTemplate;
    /**
     * 保存或者修改活动
     * @param activitySaveReqDTO 活动信息
     */
    @Override
    public void save(ActivitySaveReqDTO activitySaveReqDTO) {
        //1.校验逻辑
        activitySaveReqDTO.check();
        //2.活动数据组装
        //转换
        Activity activity = BeanUtil.toBean(activitySaveReqDTO, Activity.class);
        //状态：待生效
        activity.setStatus(ActivityStatusEnum.NO_DISTRIBUTE.getStatus());
        //库存
        activity.setStockNum(activitySaveReqDTO.getStockNum());
        //主键
        if (activitySaveReqDTO.getId() == null) {
            activity.setId(IdUtils.getSnowflake().nextId());
        }
        //3.保存或修改
        saveOrUpdate(activity);

    }

    /**
     * 根据活动id查询活动信息
     *
     * @param id 活动id
     * @return
     */
    @Override
    public Activity queryById(Long id) {
        //1.查询数据库
        Activity activity = getById(id);
        if (activity == null){
            return new Activity();
        }
//        //2.组装返回
//        ActivityInfoResDTO activityInfoResDTO = BeanUtil.toBean(activity, ActivityInfoResDTO.class);
//        //领取数量：发放数量-库存
//        Integer receiveNum = activity.getTotalNum() - activity.getStockNum();
//        activityInfoResDTO.setReceiveNum(receiveNum);
//        //核销数量：查询该活动id的核销表数量
//        Integer writeOffNum = couponWriteOffService.lambdaQuery().eq(CouponWriteOff::getActivityId, activity.getId()).count();
//        activityInfoResDTO.setWriteOffNum(writeOffNum);

        return activity;
    }

    /**
     * 活动撤销
     * @param id 活动id
     */
    @Override
    @Transactional
    public void revoke(Long id) {
        //1.活动作废
        boolean update = lambdaUpdate()
                // 设置活动状态为已作废
                .set(Activity::getStatus, ActivityStatusEnum.VOIDED.getStatus())
                // 条件：匹配活动ID
                .eq(Activity::getId, id)
                //条件：活动当前状态为待生效和进行中
                .in(Activity::getStatus, Arrays.asList(ActivityStatusEnum.NO_DISTRIBUTE.getStatus(), ActivityStatusEnum.DISTRIBUTING.getStatus()))
                .update();
        if (!update){
            throw new LogicException("活动撤销失败！");
        }
        //2.未使用的优惠券作废
        couponService.lambdaUpdate()
                // 设置优惠券状态为已作废
                .set(Coupon::getStatus, CouponStatusEnum.VOIDED.getStatus())
                // 条件：匹配活动ID
                .eq(Coupon::getActivityId,id)
                // 条件：优惠券当前状态为未使用
                .in(Coupon::getStatus,CouponStatusEnum.NO_USE.getStatus())
                // 执行更新操作
                .update();

        //3.设置优惠券库存为0
        redisTemplate.opsForHash().put(String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK,id % 10),id,0);

    }

    /**
     * 分页查询活动列表
     *
     * @param
     */
    @Override
    public Pager<Activity> queryForPage(ActivityQueryForPageReqDTO activity) {
        //1.设置分页参数
        Page<Activity> page=new Page<>(activity.getPageNum(), activity.getPageSize());
        //2.设置查询条件
        System.err.println(StringUtils.isNotEmpty(activity.getName()));
        Page<Activity> activityPage= lambdaQuery()
                //活动名称
                .like(StringUtils.isNotEmpty(activity.getName()), Activity::getName, activity.getName())
                //类型，，1：满减，2：折扣
                .eq(ObjectUtil.isNotNull(activity.getType()), Activity::getType, activity.getType())
                //优惠券配置状态，1：待生效，2：进行中，3：已失效 4：作废
                .eq(ObjectUtil.isNotNull(activity.getStatus()), Activity::getStatus, activity.getStatus())
                //排序
                .orderByDesc(Activity::getCreateTime)
                .page(page);


        return Pager.build(activityPage);

    }

    /**
     * 用户端抢卷页面列表
     * @param tabType 类型
     * @return
     */
    @Override
    public List<SeizeCouponInfoResDTO> queryForListForCache(Integer tabType) {
        //1.从redis查询到活动信息
        Object seizeCouponInfoStr = redisTemplate.opsForValue().get(RedisConstants.RedisKey.ACTIVITY_CACHE_LIST);
        //如果缓存中没有数据，则返回空列表
        if (ObjectUtil.isNull(seizeCouponInfoStr)){
            return CollUtil.newArrayList();
        }
        //2.将json转为list
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = JsonUtils.toList(seizeCouponInfoStr.toString(), SeizeCouponInfoResDTO.class);

        //3.根据tabType确定要查询的状态:如果状态是1抢单就查询进行中，如果状态是2抢单就查询待生效
        int queryStatus=tabType== TabTypeConstants.SEIZING?ActivityStatusEnum.DISTRIBUTING.getStatus():ActivityStatusEnum.NO_DISTRIBUTE.getStatus();

        //4.过滤数据，并设置剩余数量，实际状态
        List<SeizeCouponInfoResDTO> collect = seizeCouponInfoResDTOS.stream()
                .filter(item -> queryStatus == getStatus(item.getDistributeStartTime(), item.getDistributeEndTime(), item.getStatus()))
                .peek(item -> {
                    //剩余数量
                    item.setRemainNum(item.getStockNum());
                    //状态
                    item.setStatus(queryStatus);
                }).collect(Collectors.toList());
        return collect;
    }

    public static void main(String[] args) {
        LocalDateTime now = DateUtils.now();
        System.out.println(now.plusDays(30));
    }
    /**
     * 优惠券活动预热，写入缓存
     */
    @Override
    public void preHead() {
        //1.查询进行中还未到结束时间的优惠券活动，一个月内待开始的优惠券活动
        LocalDateTime now = DateUtils.now();
        List<Activity> list = lambdaQuery()
                .le(Activity::getDistributeStartTime, now.plusDays(30))//1个月内即将开始(le小于等于)
                .in(Activity::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus()))//查询待开始和进行中
                .orderByAsc(Activity::getDistributeStartTime)
                .list();
        if (CollUtil.isEmpty(list)){
            list=new ArrayList<>();
        }
        //2.将活动列表写入缓存
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = BeanUtil.copyToList(list, SeizeCouponInfoResDTO.class);
        String toJsonStr = JsonUtils.toJsonStr(seizeCouponInfoResDTOS);
        redisTemplate.opsForValue().set(RedisConstants.RedisKey.ACTIVITY_CACHE_LIST,toJsonStr);
        //3.将待生效的活动库存写入redis
        list.stream()
                .filter(v->getStatus(v.getDistributeStartTime(),v.getDistributeEndTime(),v.getStatus())==1)
                .forEach(v->{
                    redisTemplate.opsForHash().put(String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK,v.getId() % 10),v.getId(),v.getStockNum());
                });
        //4.对于已生效的活动库存没有同步时在进行同步
        list.stream()
                .filter(v->getStatus(v.getDistributeStartTime(),v.getDistributeEndTime(),v.getStatus())==2)
                .forEach(v->{
                    redisTemplate.opsForHash().putIfAbsent(String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK,v.getId() % 10),v.getId(),v.getStockNum());
                });
    }

    @Override
    public void updateStatus() {
        //获取当前时间
        LocalDateTime now = DateUtils.now();
        //1.更新已经进行中的状态
        lambdaUpdate()
                .set(Activity::getStatus, DISTRIBUTING.getStatus())//设置为进行中
                .eq(Activity::getStatus,NO_DISTRIBUTE.getStatus())//条件：待生效
                .le(Activity::getDistributeStartTime,now)//条件：开始时间小于等于当前时间
                .gt(Activity::getDistributeEndTime,now)//条件：结束时间大于当前时间
                .update();
        //2.更新已经失效的状态
        lambdaUpdate()
                .set(Activity::getStatus, LOSE_EFFICACY.getStatus())//设置为失效
                .in(Activity::getStatus,Arrays.asList(DISTRIBUTING.getStatus(),NO_DISTRIBUTE.getStatus()))//条件：待生效或进行中
                .lt(Activity::getDistributeEndTime,now)//条件：结束时间小于当前时间
                .update();
    }

    /**
     * 根据id查询活动信息，优先从缓存中查询，缓存没有则从数据库中查询
     * @param id 活动id
     * @return
     */
    @Override
    public ActivityInfoResDTO getActivityInfoByIdFromCache(Long id) {
        //1.从缓存中获取活动信息
        Object object = redisTemplate.opsForValue().get(RedisConstants.RedisKey.ACTIVITY_CACHE_LIST);
        if (ObjectUtil.isNull(object)){
            return null;
        }
        //2.过滤指定活动信息
        List<ActivityInfoResDTO> list = JsonUtils.toList(object.toString(), ActivityInfoResDTO.class);
        if (CollUtil.isEmpty(list)){
            return null;
        }
        //3.过滤指定活动:从一个列表 list 中查找具有指定 id 的对象，并返回找到的第一个对象；如果没有找到，则返回 null。
        return list.stream().filter(v->v.getId().equals(id)).findFirst().orElse(null);
    }

    /**
     * 扣减库存
     * @param id 活动id
     */
    @Override
    public void deductStock(Long id) {
        // 尝试更新数据库中的活动记录，将指定活动的库存数量减1
        boolean update = lambdaUpdate()
                .setSql("stock_num = stock_num-1")
                .eq(Activity::getId, id)
                .gt(Activity::getStockNum, 0)
                .update();
        // 如果更新失败，说明库存可能已经为0或者活动不存在，抛出异常
        if (!update) {
            throw new LogicException("扣减优惠券库存失败，活动id：" + id);
        }
    }


    /**
     * 获取状态，用于xxl或其他定时任务在高性能要求下无法做到实时状态
     *
     * @param distributeStartTime 活动开始时间
     * @param distributeEndTime   活动结束时间
     * @param status              当前状态
     * @return 根据当前时间和活动时间返回新的状态
     */
    private int getStatus(LocalDateTime distributeStartTime, LocalDateTime distributeEndTime, Integer status) {
        // 获取当前时间
        LocalDateTime now = DateUtils.now();
        // 如果当前状态为待生效，且当前时间在活动开始时间之后和活动结束时间之前
        if (ActivityStatusEnum.NO_DISTRIBUTE.equals(status) &&
                distributeStartTime.isBefore(now) &&
                distributeEndTime.isAfter(now)) {//待生效状态，实际活动已开始
            //状态等于1待生效并且活动开始时间在当前时间之前，并且活动结束时间在当前时间之后
            return ActivityStatusEnum.DISTRIBUTING.getStatus();

        } else if (ActivityStatusEnum.NO_DISTRIBUTE.equals(status) &&
                distributeEndTime.isBefore(now)) {//待生效状态，实际活动已结束
            //状态等于1待生效并且活动结束时间在当前时间之前，表示实际活动已经结束
            return LOSE_EFFICACY.getStatus();

        } else if (ActivityStatusEnum.DISTRIBUTING.equals(status) &&
                distributeEndTime.isBefore(now)) {//进行中状态，实际活动已结束
            //状态等于2进行中，并且活动结束时间在当前时间之前表示实际活动已经结束了
            return ActivityStatusEnum.LOSE_EFFICACY.getStatus();
        }
        // 如果以上条件都不满足，返回原始状态
        return status;
    }
}
